// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/wm/core/cursor_manager.h"

#include <utility>

#include "base/logging.h"
#include "base/macros.h"
#include "ui/aura/client/cursor_client_observer.h"
#include "ui/wm/core/native_cursor_manager.h"
#include "ui/wm/core/native_cursor_manager_delegate.h"

namespace wm {

namespace internal {

    // Represents the cursor state which is composed of cursor type, visibility, and
    // mouse events enable state. When mouse events are disabled, the cursor is
    // always invisible.
    class CursorState {
    public:
        CursorState()
            : cursor_(ui::kCursorNone)
            , visible_(true)
            , cursor_set_(ui::CURSOR_SET_NORMAL)
            , mouse_events_enabled_(true)
            , visible_on_mouse_events_enabled_(true)
        {
        }

        gfx::NativeCursor cursor() const { return cursor_; }
        void set_cursor(gfx::NativeCursor cursor) { cursor_ = cursor; }

        bool visible() const { return visible_; }
        void SetVisible(bool visible)
        {
            if (mouse_events_enabled_)
                visible_ = visible;
            // Ignores the call when mouse events disabled.
        }

        ui::CursorSetType cursor_set() const { return cursor_set_; }
        void set_cursor_set(ui::CursorSetType cursor_set)
        {
            cursor_set_ = cursor_set;
        }

        bool mouse_events_enabled() const { return mouse_events_enabled_; }
        void SetMouseEventsEnabled(bool enabled)
        {
            if (mouse_events_enabled_ == enabled)
                return;
            mouse_events_enabled_ = enabled;

            // Restores the visibility when mouse events are enabled.
            if (enabled) {
                visible_ = visible_on_mouse_events_enabled_;
            } else {
                visible_on_mouse_events_enabled_ = visible_;
                visible_ = false;
            }
        }

    private:
        gfx::NativeCursor cursor_;
        bool visible_;
        ui::CursorSetType cursor_set_;
        bool mouse_events_enabled_;

        // The visibility to set when mouse events are enabled.
        bool visible_on_mouse_events_enabled_;

        DISALLOW_COPY_AND_ASSIGN(CursorState);
    };

} // namespace internal

CursorManager::CursorManager(scoped_ptr<NativeCursorManager> delegate)
    : delegate_(std::move(delegate))
    , cursor_lock_count_(0)
    , current_state_(new internal::CursorState)
    , state_on_unlock_(new internal::CursorState)
{
}

CursorManager::~CursorManager()
{
}

void CursorManager::SetCursor(gfx::NativeCursor cursor)
{
    state_on_unlock_->set_cursor(cursor);
    if (cursor_lock_count_ == 0 && GetCursor() != state_on_unlock_->cursor()) {
        delegate_->SetCursor(state_on_unlock_->cursor(), this);
    }
}

gfx::NativeCursor CursorManager::GetCursor() const
{
    return current_state_->cursor();
}

void CursorManager::ShowCursor()
{
    state_on_unlock_->SetVisible(true);
    if (cursor_lock_count_ == 0 && IsCursorVisible() != state_on_unlock_->visible()) {
        delegate_->SetVisibility(state_on_unlock_->visible(), this);
        FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_,
            OnCursorVisibilityChanged(true));
    }
}

void CursorManager::HideCursor()
{
    state_on_unlock_->SetVisible(false);
    if (cursor_lock_count_ == 0 && IsCursorVisible() != state_on_unlock_->visible()) {
        delegate_->SetVisibility(state_on_unlock_->visible(), this);
        FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_,
            OnCursorVisibilityChanged(false));
    }
}

bool CursorManager::IsCursorVisible() const
{
    return current_state_->visible();
}

void CursorManager::SetCursorSet(ui::CursorSetType cursor_set)
{
    state_on_unlock_->set_cursor_set(cursor_set);
    if (GetCursorSet() != state_on_unlock_->cursor_set())
        delegate_->SetCursorSet(state_on_unlock_->cursor_set(), this);
}

ui::CursorSetType CursorManager::GetCursorSet() const
{
    return current_state_->cursor_set();
}

void CursorManager::EnableMouseEvents()
{
    state_on_unlock_->SetMouseEventsEnabled(true);
    if (cursor_lock_count_ == 0 && IsMouseEventsEnabled() != state_on_unlock_->mouse_events_enabled()) {
        delegate_->SetMouseEventsEnabled(state_on_unlock_->mouse_events_enabled(),
            this);
    }
}

void CursorManager::DisableMouseEvents()
{
    state_on_unlock_->SetMouseEventsEnabled(false);
    if (cursor_lock_count_ == 0 && IsMouseEventsEnabled() != state_on_unlock_->mouse_events_enabled()) {
        delegate_->SetMouseEventsEnabled(state_on_unlock_->mouse_events_enabled(),
            this);
    }
}

bool CursorManager::IsMouseEventsEnabled() const
{
    return current_state_->mouse_events_enabled();
}

void CursorManager::SetDisplay(const gfx::Display& display)
{
    delegate_->SetDisplay(display, this);
}

void CursorManager::LockCursor()
{
    cursor_lock_count_++;
}

void CursorManager::UnlockCursor()
{
    cursor_lock_count_--;
    DCHECK_GE(cursor_lock_count_, 0);
    if (cursor_lock_count_ > 0)
        return;

    if (GetCursor() != state_on_unlock_->cursor()) {
        delegate_->SetCursor(state_on_unlock_->cursor(), this);
    }
    if (IsMouseEventsEnabled() != state_on_unlock_->mouse_events_enabled()) {
        delegate_->SetMouseEventsEnabled(state_on_unlock_->mouse_events_enabled(),
            this);
    }
    if (IsCursorVisible() != state_on_unlock_->visible()) {
        delegate_->SetVisibility(state_on_unlock_->visible(),
            this);
    }
}

bool CursorManager::IsCursorLocked() const
{
    return cursor_lock_count_ > 0;
}

void CursorManager::AddObserver(
    aura::client::CursorClientObserver* observer)
{
    observers_.AddObserver(observer);
}

void CursorManager::RemoveObserver(
    aura::client::CursorClientObserver* observer)
{
    observers_.RemoveObserver(observer);
}

bool CursorManager::ShouldHideCursorOnKeyEvent(
    const ui::KeyEvent& event) const
{
    return false;
}

void CursorManager::CommitCursor(gfx::NativeCursor cursor)
{
    current_state_->set_cursor(cursor);
}

void CursorManager::CommitVisibility(bool visible)
{
    // TODO(tdanderson): Find a better place for this so we don't
    // notify the observers more than is necessary.
    FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_,
        OnCursorVisibilityChanged(visible));
    current_state_->SetVisible(visible);
}

void CursorManager::CommitCursorSet(ui::CursorSetType cursor_set)
{
    current_state_->set_cursor_set(cursor_set);
}

void CursorManager::CommitMouseEventsEnabled(bool enabled)
{
    current_state_->SetMouseEventsEnabled(enabled);
}

} // namespace wm
